oo::class create SearchMenu {
    superclass AppMenu
    variable Menu Label NotebookObj Notebook PopupSearchDialog PopupReplaceDialog \
        Replacement

    constructor {menubar} {
        next $menubar search
        set PopupSearchDialog [PopupSearchDialog new .search]
        set PopupReplaceDialog [PopupReplaceDialog new .replace]
        set Replacement {}
    }

    method createMenu {} {
        foreach row {
            {command {{Find next} F3 {} searchNext}}
            {command {{Find prev} A-F3 {} searchPrevious}}
            {separator {}}
            {command {{Replace next} {} {} replaceNext}}
            {command {{Replace prev} {} {} replacePrevious}}
            {command {{Replace all} {} {} replaceAll}}
            {separator {}}
            {command {Clear Escape {} +clearSearchState}}
        } {
            my createItem $row
        }
    }

    method popupSearchDialog {} {
        return [$PopupSearchDialog popup "Search Word"]
    }

    method searchToward {dir} {
        # dir: nextrange prevrange
        set padWidget [my getCurrentPadWidget]
        if {[$padWidget tag range search] == {}} {
            set searchWord [my popupSearchDialog]
            if {$searchWord != {}} {
                my setSearchTag $padWidget $searchWord
            }
            set searchFrom insert-1c
        } else {
            set searchFrom insert
        }

        my setInsertMarkToSearchTag $padWidget $searchFrom $dir
    }

    method getNearbySearchTag {padWidget insertId dir} {
        set edge 0
        if {$dir == "nextrange"} {
            set op >
        } else {
            set op <
        }
        set searchTagFirst {}; set searchId {}; set r {}
        foreach {r _} [$padWidget tag ranges search] {
            lappend searchTagFirst $r
        }
        if {$r == {}} {return}
        if {$dir == "prevrange"} {set searchTagFirst [lreverse $searchTagFirst]}

        foreach id $searchTagFirst {
            if [$padWidget compare $id $op $insertId] {
                set searchId $id
                break
            }
        }
        if {$searchId == {}} {
            set searchId [lindex $searchTagFirst $edge]
        }
        return $searchId
    }

    method setInsertMarkToSearchTag {padWidget insertId dir} {
        if {[set searchId [my getNearbySearchTag $padWidget $insertId $dir]] != {}} {
            $padWidget mark set insert $searchId
            $padWidget see insert
        }
    }

    method searchNext {} {
        my searchToward nextrange
    }

    method searchPrevious {} {
        my searchToward prevrange
    }

    method clearSearchState {} {
        set padWidget [my getCurrentPadWidget]
        $padWidget tag remove search 1.0 end
    }

    method setSearchTag {padWidget searchWord} {
        set len [string length $searchWord]
        set ids [$padWidget search -all $searchWord 1.0]
        foreach id $ids {
            $padWidget tag add search $id $id+${len}c
        }
    }

    method popupReplaceDialog {} {
        return [$PopupReplaceDialog popup "Replace word"]
    }

    method replaceToward {dir} {
        set padWidget [my getCurrentPadWidget]
        if {[$padWidget tag range search] == {}} {
            foreach {searchWord Replacement} [my popupReplaceDialog] {
                if {$searchWord != {}} {
                    my setSearchTag $padWidget $searchWord
                }
            }
            set replaceFrom insert-1c
        } else {
            set replaceFrom insert
        }

        my replaceWord $padWidget $replaceFrom $Replacement $dir
    }

    method replaceWord {padWidget insertId replacement dir} {
        if {[set searchId [my getNearbySearchTag $padWidget $insertId $dir]] != {}} {
            set end [lindex [$padWidget tag nextrange search $searchId] 1]
            $padWidget replace $searchId $end $replacement
        }
    }

    method replaceNext {} {
        my replaceToward nextrange
    }

    method replacePrevious {} {
        my replaceToward prevrange
    }

    method replaceAll {} {
        my replaceNext
        set padWidget [my getCurrentPadWidget]
        if {[$padWidget tag range search] != {}} {
            tailcall [self object] replaceNext
        }
    }
}